 /*
  * Copyright (C) yajin 2008 <yajinzhou@gmail.com >
  *     
  * This file is part of the virtualmips distribution. 
  * See LICENSE file for terms of the license. 
  *
  */


 /*Pavo emulation
    http://www.ingenic.cn/pfwebplus/productServ/kfyd/Hardware/pffaqQuestionContent.aspx?Category=2&Question=3
  */


#define _GNU_SOURCE
#include<string.h>
#include <assert.h>
#include<stdlib.h>
#include <confuse.h>

#include "vp_lock.h"
#include "utils.h"
#include "mips64.h"
#include "vm.h"
#include "cpu.h"
#include "mips64_exec.h"
#include "debug.h"

#include "pavo.h"
#include "device.h"
#include "dev_cs8900.h"

extern m_uint32_t jz4740_int_table[JZ4740_INT_INDEX_MAX];
int dev_jz4740_gpio_init(vm_instance_t * vm, char *name, m_pa_t paddr, m_uint32_t len);
int dev_jz4740_uart_init(vm_instance_t * vm, char *name, m_pa_t paddr, m_uint32_t len, u_int irq, vtty_t * vtty);
int dev_jz4740_cpm_init(vm_instance_t * vm, char *name, m_pa_t paddr, m_uint32_t len);
int dev_jz4740_emc_init(vm_instance_t * vm, char *name, m_pa_t paddr, m_uint32_t len);
int dev_jz4740_rtc_init(vm_instance_t * vm, char *name, m_pa_t paddr, m_uint32_t len);
int dev_jz4740_wdt_tcu_init(vm_instance_t * vm, char *name, m_pa_t paddr, m_uint32_t len);
int dev_jz4740_int_init(vm_instance_t * vm, char *name, m_pa_t paddr, m_uint32_t len);
int dev_jz4740_dma_init(vm_instance_t * vm, char *name, m_pa_t paddr, m_uint32_t len);
int dev_jz4740_lcd_init(vm_instance_t * vm, char *name, m_pa_t paddr, m_uint32_t len);

void dev_jz4740_gpio_setirq(int irq);
void dev_jz4740_gpio_clearirq(int irq);


static mips_def_t jz4740_def =
    {
        .name = "jz4740",
        .CP0_PRid = 0x0ad0024f,
        .config_usable = 0x83,
        .CP0_Config0 = 0x80000082,
        .CP0_Config1 = 0x3E613080 ,
        .CP0_Config7 = 0,
        .SEGBITS = 32,
        .PABITS = 32,
        .pc=0x80000004,
        .tlb_entries =32,
        .address_model = 32,
    };




int pavo_init_cs8900(pavo_t * pavo, char *name, m_pa_t paddr, m_uint32_t len, int irq_no)
{

    struct vm_instance *vm = pavo->vm;

    int nio_type = -1;
    netio_desc_t *nio;
    int count;
    char *tokens[10];
    struct cs8900_data *d;

    if ((count = m_strsplit(pavo->cs8900_iotype, ':', tokens, 10)) < 2)
    {
        vm_error(vm, "unable to parse NIO description '%s'.\n", pavo->cs8900_iotype);
        return (-1);
    }
    nio_type = netio_get_type(tokens[0]);

    switch (nio_type)
    {
    case NETIO_TYPE_TAP:
        nio = netio_desc_create_tap(name, tokens[1]);
        break;
        //case NETIO_TYPE_LINUX_ETH:
        //      nio=netio_desc_create_lnxeth(name,tokens[1]);
        //      break;
    default:
        return (-1);
    }
    if (!nio)
    {
        vm_error(vm, "unable to create NETIO  descriptor %s\n", tokens[0]);
        return (-1);
    }
    d = dev_cs8900_init(vm, name, paddr, len, irq_no);
    if (!d)
    {
        vm_error(vm, "unable to int cs8900\n");
        return (-1);
    }
    if (dev_cs8900_set_nio(d, nio) == -1)
    {
        vm_error(vm, "unable to set cs8900 nio \n");
        return (-1);
    }

    return 0;

}

/* Initialize the PAVO Platform (MIPS) */
static int pavo_init_platform(pavo_t * pavo)
{
    struct vm_instance *vm = pavo->vm;
    cpu_mips_t *cpu;
    void *(*cpu_run_fn) (void *);

    vm_init_vtty(vm);

    /* Initialize the virtual MIPS processor */
    if (!(cpu = cpu_create(vm, CPU_TYPE_MIPS32, 0)))
    {
        vm_error(vm, "unable to create CPU0!\n");
        return (-1);
    }
    cpu_register(cpu, &jz4740_def);
    cpu_init(cpu);

    vm->cpu = cpu;

    cpu_run_fn = (void *) mips64_exec_run_cpu;
    /* create the CPU thread execution */
    if (pthread_create(&cpu->cpu_thread, NULL, cpu_run_fn, cpu) != 0)
    {
        fprintf(stderr, "cpu_create: unable to create thread for CPU%u\n", 0);
        free(cpu);
        return (-1);
    }

    /* Initialize RAM */
    vm_ram_init(vm, 0x00000000ULL);

    /*create 1GB nand flash */
    if ((vm->configure->flash_size == 0x400) && (vm->configure->flash_type = FLASH_TYPE_NAND_FLASH))
        if (dev_nand_flash_1g_init(vm, "NAND FLASH 1G", NAND_DATAPORT, 0x10004, &(pavo->nand_flash)) == -1)
            return (-1);
    if (dev_jz4740_gpio_init(vm, "JZ4740 GPIO", JZ4740_GPIO_BASE, JZ4740_GPIO_SIZE) == -1)
        return (-1);
    if (dev_jz4740_uart_init(vm, "JZ4740 UART0", JZ4740_UART0_BASE, JZ4740_UART0_SIZE, 9, vm->vtty_con1) == -1)
        return (-1);
    if (dev_jz4740_uart_init(vm, "JZ4740 UART1", JZ4740_UART1_BASE, JZ4740_UART1_SIZE, 8, vm->vtty_con2) == -1)
        return (-1);
    if (dev_jz4740_cpm_init(vm, "JZ4740 CPM", JZ4740_CPM_BASE, JZ4740_CPM_SIZE) == -1)
        return (-1);
    if (dev_jz4740_emc_init(vm, "JZ4740 EMC", JZ4740_EMC_BASE, JZ4740_EMC_SIZE) == -1)
        return (-1);
    if (dev_jz4740_rtc_init(vm, "JZ4740 RTC", JZ4740_RTC_BASE, JZ4740_RTC_SIZE) == -1)
        return (-1);
    if (dev_jz4740_wdt_tcu_init(vm, "JZ4740 WDT/TCU", JZ4740_WDT_TCU_BASE, JZ4740_WDT_TCU_SIZE) == -1)
        return (-1);
    if (dev_jz4740_int_init(vm, "JZ4740 INT", JZ4740_INT_BASE, JZ4740_INT_SIZE) == -1)
        return (-1);
    if (dev_jz4740_dma_init(vm, "JZ4740 DMA", JZ4740_DMA_BASE, JZ4740_DMA_SIZE) == -1)
        return (-1);

    if (pavo->cs8900_enable == 1)
    {
        if (pavo_init_cs8900(pavo, "CS8900A", CS8900_IO_BASE, CS8900_SIZE, CS8900_DEFAULT_IRQ) == -1)
            return (-1);
    }

     /*LCD*/
#ifdef SIM_LCD
        if (dev_jz4740_lcd_init(vm, "JZ4740 LCD", JZ4740_LCD_BASE, JZ4740_LCD_SIZE) == -1)
        return (-1);
#endif

    return (0);
}

static int pavo_boot(pavo_t * pavo)
{
    vm_instance_t *vm = pavo->vm;

    if (!vm->cpu)
        return (-1);

    return jz4740_reset(vm);
}

void pavo_clear_irq(vm_instance_t * vm, u_int irq)
{
    m_uint32_t irq_mask;

    irq_mask = 1 << irq;

    /*clear ISR and IPR */
    jz4740_int_table[INTC_ISR / 4] &= ~irq_mask;
    jz4740_int_table[INTC_IPR / 4] &= ~irq_mask;


}

/*map irq to soc irq*/
int forced_inline plat_soc_irq(u_int irq)
{
    if ((irq >= 48) && (irq <= 175))
    {
        dev_jz4740_gpio_setirq(irq);
        /*GPIO IRQ */
        if ((irq >= 48) && (irq <= 79))
            irq = IRQ_GPIO0;
        else if ((irq >= 80) && (irq <= 111))
            irq = IRQ_GPIO1;
        else if ((irq >= 112) && (irq <= 143))
            irq = IRQ_GPIO2;
        else if ((irq >= 144) && (irq <= 175))
            irq = IRQ_GPIO3;
    }
    return irq;
}


void pavo_set_irq(vm_instance_t * vm, u_int irq)
{
    m_uint32_t irq_mask;


    irq = plat_soc_irq(irq);

    irq_mask = 1 << irq;
    jz4740_int_table[INTC_ISR / 4] |= irq_mask;
    /*first check ICMR. masked interrupt is **invisible** to cpu */
    if (unlikely(jz4740_int_table[INTC_IMR / 4] & irq_mask))
    {
        /*the irq is masked. clear IPR */
        jz4740_int_table[INTC_IPR / 4] &= ~irq_mask;
    }
    else
    {
        /*the irq is not masked */

        /*set IPR */
        /*
           we set IPR, not *or* . yajin

           JZ Kernel 'plat_irq_dispatch' determine which is the highest priority interrupt 
           and handle. 
           It uses a function ffs to find first set irq from least bit to highest bit.
           260         irq = ffs(intc_ipr) - 1;

           That means when tcu0 irq and gpio1 irq occurs at the same time ,INTC_IPR=0x8800000
           and irq handler will handle tcu0 irq(bit 23) not gpio1 irq(bit 27).

           In pavo gpio1->cs8900 int

           TCU0 irq occurs every 10 ms and gpio1 occurs about 10ms (cs8900 has received a packet 
           or has txed a packet), jz kernel always handle tcu0 irq. gpio1 irq is hungry. So I just set 
           jz4740_int_table[INTC_IPR/4]= irq_mask not or(|) irq_mask. TCU0 irq may be lost. However,
           gpio1 irq is not so ofen so it is not a big problem.

           In emulator, irq is not a good method for hardware to tell kernel something has happened.
           Emulator likes polling more than interrupt :) .

         */
        jz4740_int_table[INTC_IPR / 4] = irq_mask;


        mips64_set_irq(vm->cpu, JZ4740_INT_TO_MIPS);
        mips64_update_irq_flag(vm->cpu);
    }
}

COMMON_CONFIG_INFO_ARRAY;
static void printf_configure(pavo_t * pavo)
{

    vm_instance_t *vm = pavo->vm;
    PRINT_COMMON_COFING_OPTION;

    /*print other configure information here */
    if (pavo->cs8900_enable == 1)
    {
        printf("CS8900 net card enabled\n");
        printf("CS8900 iotype %s \n", pavo->cs8900_iotype);
    }
    else
        printf("CS8900 net card disenabled\n");
}
static void pavo_parse_configure(pavo_t * pavo)
{
    vm_instance_t *vm = pavo->vm;
    cfg_opt_t opts[] = {
        COMMON_CONFIG_OPTION
            /*add other configure information here */
            CFG_SIMPLE_INT("cs8900_enable", &(pavo->cs8900_enable)),
        CFG_SIMPLE_STR("cs8900_iotype", &(pavo->cs8900_iotype)),

        CFG_END()
    };
    cfg_t *cfg;

    cfg = cfg_init(opts, 0);
    cfg_parse(cfg, vm->configure_filename);
    cfg_free(cfg);

    VALID_COMMON_CONFIG_OPTION;

    if (pavo->cs8900_enable == 1)
    {
        ASSERT(pavo->cs8900_iotype != NULL, "You must set cs8900_enable \n");
    }

    /*Print the configure information */
    printf_configure(pavo);

}

/* Create a router instance */
vm_instance_t *create_instance(char *configure_filename)
{
    pavo_t *pavo;
    char *name;
    if (!(pavo = malloc(sizeof(*pavo))))
    {
        fprintf(stderr, "PAVO': Unable to create new instance!\n");
        return NULL;
    }

    memset(pavo, 0, sizeof(*pavo));
    name = strdup("pavo");

    if (!(pavo->vm = vm_create(name, VM_TYPE_PAVO)))
    {
        fprintf(stderr, "PAVO : unable to create VM instance!\n");
        goto err_vm;
    }
    free(name);

    if (configure_filename == NULL)
        pavo->vm->configure_filename = strdup(PAVO_DEFAULT_CONFIG_FILE);
    else
        pavo->vm->configure_filename = strdup(configure_filename);
    pavo_parse_configure(pavo);
    /*init gdb debug */
    vm_debug_init(pavo->vm);

    pavo->vm->hw_data = pavo;

    return (pavo->vm);

  err_vm:
    free(name);
    free(pavo);
    return NULL;

}


int init_instance(vm_instance_t * vm)
{
    pavo_t *pavo = VM_PAVO(vm);


    if (pavo_init_platform(pavo) == -1)
    {
        vm_error(vm, "unable to initialize the platform hardware.\n");
        return (-1);
    }
    /* IRQ routing */
    vm->set_irq = pavo_set_irq;
    vm->clear_irq = pavo_clear_irq;

    return (pavo_boot(pavo));

}
